Skip to content

turbo-tasks: task-storage memory wins#93720

Merged
lukesandberg merged 5 commits into
canaryfrom
05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec
May 20, 2026
Merged

turbo-tasks: task-storage memory wins#93720
lukesandberg merged 5 commits into
canaryfrom
05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec

Conversation

@lukesandberg

@lukesandberg lukesandberg commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Four small, independent changes that shrink TaskStorage and the data it owns:

Recommend reviewing commit-by-commit

  1. Arc<CachedTaskType>triomphe::Arc<CachedTaskType>. triomphe::Arc is already a workspace dep used in ReadRef / SharedReference. CachedTaskType never appears in a Weak<...>, so we can drop the weak count and the CAS in drop_slow. Saves one usize per allocation. Migrated via a CachedTaskTypeArc newtype so the bincode Encode/Decode impls don't need to cross the orphan rule.

  2. Niche-encode CellDependency. The cell_dependencies / cell_dependents sets used to hold (CellRef, Option<u64>) tuples — Option<u64> cost a full 16 B (8 B discriminant + 8 B value, aligned), making each element 32 B. A CellDependency enum with two variants (All(CellRef) / Hash(CellRef, u64)) lets the layout algorithm reuse the niche on ValueTypeId (NonZero<u16>) inside CellRef.cell.type_id for the variant tag. Element size drops 32 → 24 B; LazyField from 56 → 48 B. The same enum backs both forward and reverse edges — for cell_dependents we re-point CellRef.task at the dependent task.

    Added CellDependency::into_parts() and use it in iter_cell_dependents / iter_cell_dependencies hot loops so the discriminant is checked once instead of twice via back-to-back cell_ref() + key() calls.

  3. TaskStorage::lazy: Vec<LazyField>TinyVec<LazyField>. The lazy vec only ever holds ~25 elements (one per declared lazy field in the schema). Swapping Vec's 24 B (ptr, len, cap) header for (ptr, len: u8, cap: u8) + 6 B padding gives 16 B. Drops size_of::<TaskStorage>() from 136 → 128 B.

    TinyVec is hand-rolled so I added a push/iter micro-benchmark to confirm it doesn't lose performance vs std Vec. Results below.

  4. Rightsize collections → Explore the AutoSet/AutoMap types in storage_schema and ensure each one is maximally sized for its natural alignment.

Benchmark results

next build on a representative app (15 runs each, M4 Pro, caffeinate -dimsu nice -n -20)

Fresh same-day baseline against branch:

metric canary branch Δ 95% CI significant?
wall time 40.83s 41.12s +0.7% [−1.07s, +1.64s] no
user time 282.27s 283.21s +0.3% [−1.02s, +2.89s] no
sys time 69.38s 71.26s +2.7% [−1.54s, +5.32s] no
MaxRSS 12.47 GB 12.04 GB −3.4% [−0.48 GB, −0.38 GB] yes

MaxRSS is the headline. −0.43 GB on a 12.5 GB working set, with t=−17.86 (every branch run lower than every canary run, CV ≤ 0.6% on both sides). Wall / user / sys are all within noise — this PR is a memory win with no measurable timing impact.

TinyVec vs Vec micro-bench (turbo-tasks/benches/tiny_vec.rs, 200 samples each)

n Vec push TinyVec push Δ% Vec iter TinyVec iter Δ%
0 1.31ns 894ps −31.8% 598ps 596ps −0.4%
1 16.92ns 14.75ns −12.9% 964ps 952ps −1.2%
4 17.93ns 15.93ns −11.1% 1.49ns 1.50ns +0.5%
8 63.13ns 45.24ns −28.3% 1.97ns 1.96ns −0.2%
16 97.35ns 79.91ns −17.9% 3.16ns 3.14ns −0.5%
24 137.41ns 119.88ns −12.8% 4.30ns 4.30ns +0.0%

TinyVec push is 11–32% faster than Vec push across all realistic sizes; iter is identical. Run with cargo bench -p turbo-tasks --bench tiny_vec.

task_overhead/turbo Criterion bench (M4 Pro, --sample-size 200)

variant dur canary branch Δ significant?
turbo-uncached 1µs 9.77 µs 9.68 µs −1.0% yes
turbo-uncached 1000µs 1.01 ms 1.01 ms −0.1% yes
turbo-cached-same-keys 1µs 198.6 ns 191.9 ns −3.4% yes
turbo-cached-same-keys 100µs 226.5 ns 208.1 ns −8.1% yes
turbo-cached-different-keys 1µs 233.8 ns 224.1 ns −4.2% yes
turbo-cached-different-keys 100µs 305.3 ns 246.9 ns −19.1% yes
turbo-uncached-parallel 10µs 1.63 µs 1.54 µs −5.8% yes
turbo-uncached-parallel 100µs 8.41 µs 7.88 µs −6.3% yes

lukesandberg commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

@github-actions github-actions Bot added created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js. labels May 9, 2026
@github-actions

github-actions Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Tests Passed

Commit: 3192aa5

Comment thread turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs Outdated
Comment thread turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs Outdated
Comment thread turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs Outdated
@lukesandberg lukesandberg changed the title turbo-tasks: migrate CachedTaskType reference-counting to triomphe::Arc turbo-tasks: task-storage memory and dispatch-path overhead reductions May 9, 2026
Comment thread turbopack/crates/turbo-tasks-backend/src/data.rs Outdated
Comment thread turbopack/crates/turbo-tasks-backend/src/data.rs Outdated
Comment thread turbopack/crates/turbo-tasks-macros/src/derive/task_storage_macro.rs Outdated
Comment thread turbopack/crates/turbo-tasks/src/backend.rs
Comment thread turbopack/crates/turbo-tasks/src/backend.rs
Comment thread turbopack/crates/turbo-tasks/src/backend.rs Outdated
Comment thread turbopack/crates/turbo-tasks/src/lazy_vec.rs Outdated
Comment thread turbopack/crates/turbo-tasks/src/raw_vc.rs Outdated
@github-actions

github-actions Bot commented May 10, 2026

Copy link
Copy Markdown
Contributor

Stats from current PR

✅ No significant changes detected

📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 813ms 812ms █████
Cold (Ready in log) 776ms 777ms ▇██▇▇
Cold (First Request) 1.178s 1.186s ▂▇▅▂▃
Warm (Listen) 811ms 811ms █████
Warm (Ready in log) 776ms 777ms ▇██▇▇
Warm (First Request) 578ms 578ms ▄██▅▅
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 812ms 812ms ▅▅▅▁█
Cold (Ready in log) 778ms 781ms ▃▂▄▁▄
Cold (First Request) 3.208s 3.206s ▆▄▇▁▇
Warm (Listen) 812ms 812ms ▁▃▃▁▃
Warm (Ready in log) 782ms 781ms ▄▂▆▁▅
Warm (First Request) 3.206s 3.218s ▇▃█▁▇

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 4.700s 4.695s ▅█▆▅▆
Cached Build 4.733s 4.729s ▅█▆▄▅
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 23.624s 23.831s ▃▄█▂▄
Cached Build 23.957s 23.862s ▂▂█▁▄
node_modules Size 506 MB 506 MB █████
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
02kvk12tfbji8.js gzip 159 B N/A -
04hm05ar7kldw.js gzip 5.73 kB N/A -
0b5ik935l-j9n.js gzip 157 B N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0dvitrl5zg37g.js gzip 8.82 kB N/A -
0ef8okeiyr4ur.js gzip 154 B N/A -
0r_che1oemkeo.js gzip 153 B N/A -
0sf7ysou-72zd.js gzip 8.71 kB N/A -
0vbd9tz19_lrv.js gzip 70.9 kB N/A -
12orngq3y4s5j.js gzip 157 B N/A -
157abun3hwc_s.js gzip 10.3 kB N/A -
1c4h308zw2qrq.js gzip 159 B N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1jj68jv9537mc.js gzip 13.8 kB N/A -
1jpaub6y8xlfr.js gzip 2.3 kB N/A -
1ngibqy4cdev6.js gzip 153 B N/A -
1ot0mvscrc_uf.js gzip 233 B N/A -
2_m3xv2uq3sjc.js gzip 1.46 kB N/A -
20jgkju22ogb8.js gzip 50.4 kB N/A -
24y34mwgrkqp4.js gzip 8.78 kB N/A -
2c-fd4y1zozz8.js gzip 8.79 kB N/A -
2ctq21yd1vo9r.js gzip 155 B N/A -
2d7416h_xd36x.js gzip 8.71 kB N/A -
2eccaucon9zc0.js gzip 154 B N/A -
2g21ny1t2kw37.js gzip 7.61 kB N/A -
2izfitc8kpe74.js gzip 154 B N/A -
2lyuhit6rn8fy.js gzip 9.44 kB N/A -
2n052os91yo28.js gzip 151 B N/A -
2q0gr8wfr3jwl.js gzip 8.77 kB N/A -
2t9e75oz6r0zp.js gzip 8.76 kB N/A -
2uku_olcn15b7.js gzip 8.79 kB N/A -
30r8mm-46bdqy.js gzip 220 B 220 B
38plfjydl6gzt.js gzip 65.6 kB N/A -
3c1jdxkzlb8oq.js gzip 12.9 kB N/A -
3inab2jybr4k9.js gzip 450 B N/A -
3jkm5tdjvaf_q.js gzip 13.1 kB N/A -
3k7gxs6p2mqat.js gzip 154 B N/A -
3mt67agm5wp40.js gzip 10.6 kB N/A -
3saabek4kohwi.js gzip 10 kB N/A -
409cb3r0bc3ko.js gzip 167 B N/A -
4189xmby9yu1p.js gzip 13.6 kB N/A -
turbopack-04..21t4.js gzip 4.18 kB N/A -
turbopack-0e..bquc.js gzip 4.2 kB N/A -
turbopack-0e..wxz3.js gzip 4.2 kB N/A -
turbopack-0g..1et0.js gzip 4.2 kB N/A -
turbopack-1_..nogk.js gzip 4.2 kB N/A -
turbopack-1c..61a3.js gzip 4.2 kB N/A -
turbopack-1w..rjwv.js gzip 4.2 kB N/A -
turbopack-29..20ut.js gzip 4.2 kB N/A -
turbopack-2k..1umy.js gzip 4.2 kB N/A -
turbopack-2o..e2id.js gzip 4.2 kB N/A -
turbopack-2o..cn3o.js gzip 4.2 kB N/A -
turbopack-3m..xsw_.js gzip 4.2 kB N/A -
turbopack-3n..o2og.js gzip 4.2 kB N/A -
turbopack-43..qqzs.js gzip 4.21 kB N/A -
0_i7nqgx23st7.js gzip N/A 10 kB -
06puhytyxk31p.js gzip N/A 8.82 kB -
0ey1zw-mlwx2h.js gzip N/A 155 B -
0j42f9zonj0wd.js gzip N/A 13 kB -
0m34gln_kt4fg.js gzip N/A 5.73 kB -
0p4sjm1_vsyqc.js gzip N/A 157 B -
0v8bak4y0xxj2.js gzip N/A 169 B -
0vwkqhru5pnu1.js gzip N/A 156 B -
10yxp2katn2c1.js gzip N/A 156 B -
1ck72gbqbfell.js gzip N/A 50.4 kB -
1g3q1ww01thnl.js gzip N/A 2.3 kB -
1hraqxuiymq6v.js gzip N/A 8.79 kB -
1l9un1sl77287.js gzip N/A 1.46 kB -
1ydp85rsgb8o6.js gzip N/A 155 B -
21-eavqb1k_36.js gzip N/A 13.9 kB -
2147zgtf14z-q.js gzip N/A 234 B -
234uq0f6lmp4e.js gzip N/A 70.9 kB -
23bz3xsg-5-1s.js gzip N/A 8.71 kB -
27441mytv7pbm.js gzip N/A 9.43 kB -
2cjkwjgm1zcfs.js gzip N/A 8.71 kB -
2mhqz28ez110r.js gzip N/A 158 B -
2qs_wqvnpva_z.js gzip N/A 155 B -
2ry6ynqi-5ptz.js gzip N/A 152 B -
2scd8zaoyb8md.js gzip N/A 8.79 kB -
2st_qs6p_9us0.js gzip N/A 13.1 kB -
2zo2exm1d8qj1.js gzip N/A 13.6 kB -
32kwi5zoqnqhy.js gzip N/A 161 B -
36-o575nl6lr_.js gzip N/A 156 B -
3a2xhwp6l10fw.js gzip N/A 156 B -
3f710q6kll2xn.js gzip N/A 7.61 kB -
3h7n9ebdmu7d3.js gzip N/A 159 B -
3hn75zuxly9az.js gzip N/A 10.3 kB -
3hqh7m128tvsn.js gzip N/A 8.77 kB -
3hqti_t-zy1x4.js gzip N/A 449 B -
3mnawenie1flm.js gzip N/A 8.76 kB -
3ubsozlu6zs38.js gzip N/A 10.6 kB -
42qezf5_oyvsu.js gzip N/A 65.6 kB -
43iwfqjnx1cy_.js gzip N/A 8.78 kB -
turbopack-02..61i-.js gzip N/A 4.2 kB -
turbopack-03..znl6.js gzip N/A 4.2 kB -
turbopack-0i..i6-4.js gzip N/A 4.2 kB -
turbopack-0p..68pi.js gzip N/A 4.2 kB -
turbopack-0x..aj2m.js gzip N/A 4.2 kB -
turbopack-11..azqn.js gzip N/A 4.2 kB -
turbopack-1p..vrdm.js gzip N/A 4.2 kB -
turbopack-1x..g7k6.js gzip N/A 4.2 kB -
turbopack-2b.._9pw.js gzip N/A 4.2 kB -
turbopack-2j..3lug.js gzip N/A 4.2 kB -
turbopack-2v..va8i.js gzip N/A 4.2 kB -
turbopack-3f..jnwg.js gzip N/A 4.21 kB -
turbopack-3j..0r1z.js gzip N/A 4.18 kB -
turbopack-3s..utxm.js gzip N/A 4.2 kB -
Total 469 kB 469 kB ⚠️ +95 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 716 B 717 B
Total 716 B 717 B ⚠️ +1 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 433 B 433 B
Total 433 B 433 B

📦 Webpack

Client

Main Bundles
Canary PR Change
2258-HASH.js gzip 61.4 kB N/A -
2266-HASH.js gzip 4.69 kB N/A -
3317.HASH.js gzip 169 B N/A -
4866-HASH.js gzip 5.64 kB N/A -
9e302639-HASH.js gzip 62.8 kB N/A -
framework-HASH.js gzip 59.5 kB 59.5 kB
main-app-HASH.js gzip 255 B 254 B
main-HASH.js gzip 39.9 kB 39.9 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
175fd0fd-HASH.js gzip N/A 62.8 kB -
2596-HASH.js gzip N/A 5.63 kB -
34-HASH.js gzip N/A 61.3 kB -
5691.HASH.js gzip N/A 169 B -
9156-HASH.js gzip N/A 4.68 kB -
Total 236 kB 236 kB ✅ -103 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 193 B 193 B
_error-HASH.js gzip 181 B 182 B
css-HASH.js gzip 334 B 332 B
dynamic-HASH.js gzip 1.79 kB 1.81 kB
edge-ssr-HASH.js gzip 255 B 255 B
head-HASH.js gzip 351 B 348 B
hooks-HASH.js gzip 385 B 384 B
image-HASH.js gzip 580 B 580 B
index-HASH.js gzip 257 B 259 B
link-HASH.js gzip 2.51 kB 2.52 kB
routerDirect..HASH.js gzip 318 B 319 B
script-HASH.js gzip 387 B 386 B
withRouter-HASH.js gzip 316 B 316 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.97 kB 7.99 kB ⚠️ +19 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 126 kB 126 kB
page.js gzip 276 kB 270 kB 🟢 5.35 kB (-2%)
Total 402 kB 396 kB ✅ -5.57 kB
Middleware
Canary PR Change
middleware-b..fest.js gzip 621 B 614 B 🟢 7 B (-1%)
middleware-r..fest.js gzip 155 B 155 B
middleware.js gzip 44.5 kB 44.9 kB 🔴 +454 B (+1%)
edge-runtime..pack.js gzip 842 B 842 B
Total 46.1 kB 46.5 kB ⚠️ +447 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 719 B 717 B
Total 719 B 717 B ✅ -2 B
Build Cache
Canary PR Change
0.pack gzip 4.49 MB 4.49 MB 🟢 6.09 kB (0%)
index.pack gzip 115 kB 113 kB 🟢 1.84 kB (-2%)
index.pack.old gzip 114 kB 114 kB
Total 4.72 MB 4.71 MB ✅ -7.32 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 351 kB 351 kB
app-page-exp..prod.js gzip 195 kB 195 kB
app-page-tur...dev.js gzip 350 kB 350 kB
app-page-tur..prod.js gzip 194 kB 194 kB
app-page-tur...dev.js gzip 347 kB 347 kB
app-page-tur..prod.js gzip 192 kB 192 kB
app-page.run...dev.js gzip 347 kB 347 kB
app-page.run..prod.js gzip 193 kB 193 kB
app-route-ex...dev.js gzip 77.5 kB 77.5 kB
app-route-ex..prod.js gzip 52.9 kB 52.9 kB
app-route-tu...dev.js gzip 77.6 kB 77.6 kB
app-route-tu..prod.js gzip 52.9 kB 52.9 kB
app-route-tu...dev.js gzip 77.2 kB 77.2 kB
app-route-tu..prod.js gzip 52.7 kB 52.7 kB
app-route.ru...dev.js gzip 77.1 kB 77.1 kB
app-route.ru..prod.js gzip 52.7 kB 52.7 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 44.3 kB 44.3 kB
pages-api-tu..prod.js gzip 33.8 kB 33.8 kB
pages-api.ru...dev.js gzip 44.3 kB 44.3 kB
pages-api.ru..prod.js gzip 33.7 kB 33.7 kB
pages-turbo....dev.js gzip 53.7 kB 53.7 kB
pages-turbo...prod.js gzip 39.4 kB 39.4 kB
pages.runtim...dev.js gzip 53.6 kB 53.6 kB
pages.runtim..prod.js gzip 39.4 kB 39.4 kB
server.runti..prod.js gzip 63.1 kB 63.1 kB
use-cache-pr...dev.js gzip 69.7 kB 69.7 kB
use-cache-pr...dev.js gzip 69.7 kB 69.7 kB
use-cache-pr...dev.js gzip 68 kB 68 kB
use-cache-pr...dev.js gzip 68 kB 68 kB
Total 3.37 MB 3.37 MB
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/ed75068ffc352a2e87b5f50d29aaab776404fc48/next

Commit: ed75068

@lukesandberg lukesandberg force-pushed the 05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec branch from dbced22 to 50785a8 Compare May 12, 2026 16:40
@lukesandberg lukesandberg changed the title turbo-tasks: task-storage memory and dispatch-path overhead reductions turbo-tasks: task-storage memory wins May 12, 2026
@lukesandberg lukesandberg force-pushed the 05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec branch from 2f21410 to cf840de Compare May 12, 2026 21:26
@lukesandberg lukesandberg marked this pull request as ready for review May 12, 2026 21:32
@lukesandberg lukesandberg requested a review from sokra May 12, 2026 21:32
@lukesandberg lukesandberg force-pushed the 05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec branch 3 times, most recently from 7221af0 to 0938c4c Compare May 19, 2026 06:16
@lukesandberg lukesandberg requested a review from a team May 19, 2026 19:07
Comment thread turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs Outdated
Comment thread turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs Outdated
Comment thread turbopack/crates/turbo-tasks-backend/src/data.rs
Comment thread turbopack/crates/turbo-tasks-macros/src/derive/task_storage_macro.rs Outdated
@lukesandberg lukesandberg enabled auto-merge (squash) May 20, 2026 06:39
Migrate Arc<CachedTaskType> to triomphe::Arc via CachedTaskTypeArc newtype.
Saves one usize per allocation (no weak count) and avoids the weak-count
CAS in drop_slow compared to std::sync::Arc. We never need
Weak<CachedTaskType>, so the trade-off is favorable.
Replace the `(CellRef, Option<u64>)` and `(CellId, Option<u64>, TaskId)`
tuples used for cell-edge tracking with a `CellDependency` enum:

    enum CellDependency {
        All(CellRef),
        Hash(CellRef, u64),
    }

`Option<u64>` previously cost a full 16 B (8 B discriminant + 8 B value,
aligned). With an explicit enum the layout algorithm reuses the niche on
`ValueTypeId` (`NonZero<u16>`) inside `CellRef.cell.type_id` for the
variant tag, dropping the element from 32 B to 24 B. That in turn shrinks
`LazyField` from 56 B to 48 B.

Also adds `CellDependency::into_parts()` and uses it in
`iter_cell_dependents` / `iter_cell_dependencies` hot loops to avoid
checking the enum discriminant twice via back-to-back
`cell_ref()` + `key()` calls.
Replace `TaskStorage::lazy: Vec<LazyField>` with a custom 16 B `TinyVec`
(u8 len + u8 cap, the schema's max field count is well under 255).
Drops `size_of::<TaskStorage>()` from 136 B to 128 B.

Included micro-benchmarks show `TinyVec` push is 11-32% faster than `Vec`
across realistic sizes and iter is neutral.

The `TinyVec` type:

* Carries a `const MAX: u8` generic parameter that strictly caps push count
  and tightens the growth schedule (doubles until it would exceed MAX, then
  caps exactly at MAX). The schema macro emits `TinyVec<LazyField, N>` where
  `N` is the exact lazy-field count, so the cap matches the actual schema
  size (e.g. with 24 variants we end at cap=24 instead of cap=32, saving a
  few slots per fully-populated task). A compile-time static assert rejects
  `MAX = 0` at monomorphization.
* Tightens visibility: `new`, `capacity`, `as_slice`, `as_mut_slice`,
  `reserve` are private; `len` / `is_empty` stay pub as a clippy-preferred
  pair.
* Delegates `retain_mut` to `Vec::retain_mut` via a `Vec::from_raw_parts`
  round-trip — `retain_mut` is cold relative to push so the round-trip cost
  is irrelevant, and this drops ~7 unsafe blocks with a panic partial-shift
  guard.
* Delegates owned `IntoIter` to `std::vec::IntoIter` via the same Vec
  round-trip, dropping ~50 lines of unsafe.
* Drops `Extend` and `FromIterator` trait impls; the only caller path uses
  an inherent `extend_exact` which requires an `ExactSizeIterator` and
  reserves exactly once.
* Drops `iter`, `iter_mut`, `last_mut`, `Index`, `IndexMut` — all reachable
  through `Deref<Target = [T]>`. `for x in &tv` / `for x in &mut tv` still
  need `IntoIterator` impls for refs because the `for`-loop desugar doesn't
  apply `Deref` coercion across the reference boundary.

Net unsafe count in `TinyVec` is 5 in the hot path plus 1 in `retain_mut`
and 1 in `IntoIter` — each upholding a single local invariant or just
round-tripping through Vec.
Each schema field's `I` (inline capacity) was previously fixed at 1. With
`SmallVec`'s `union` feature on, the heap variant occupies 16 bytes
(`NonNull<T> + usize`), so the SmallVec body is always `max(16, N * sizeof(T))`
aligned to `max(align(T), 8)`. Net: growing `N` is free until `N * sizeof(T)`
exceeds 16, after which each step adds `align_up(sizeof(T), 8)` bytes.

Two opportunities follow:

* For lazy fields, the `LazyField` enum already pays a 40-byte payload
  budget (the largest variants — `cell_data`, `cell_data_hash`,
  `AutoSet<CellDependency>`, `CounterMap<CollectibleRef, i32>` — saturate
  it at I=1). Smaller-element fields sat at 32 B with 8 B of unused
  padding. Bumping them to fill 40 B is zero-cost: TaskStorage and
  LazyField sizes don't change.

* For inline fields on TaskStorage, raising `I` is free up to the SmallVec
  16-byte body limit (e.g. `AutoSet<TaskId>` to I=4, `CounterMap<TaskId,
  u32>` to I=2).

To allow per-field tuning, parameterize:

* `CounterMap` over `const I: usize`, propagating to its `AutoMap` inner.
* The `AutoSet` / `AutoMap` schema aliases drop their hardcoded `1`; each
  field declares its own `I`.

Per-field choices:

* `output_dependent` (inline) `AutoSet<TaskId>` → 4 (32 B)
* `upper` (inline) `CounterMap<TaskId, u32>` → 2 (32 B)
* `children`, `output_dependencies`, `outdated_output_dependencies`
  `AutoSet<TaskId>` → 6 (40 B)
* `collectibles_dependencies`, `outdated_collectibles_dependencies`
  `AutoSet<CollectiblesRef>` → 3 (40 B)
* `collectibles_dependents` `AutoSet<(TraitTypeId, TaskId)>` → 3 (40 B)
* `followers`, `aggregated_dirty_containers`,
  `aggregated_current_session_clean_containers`
  `CounterMap<TaskId, _>` → 3 (40 B)
* `cell_type_max_index` `AutoMap<ValueTypeId, u32>` → 3 (40 B)

Variants already at 40 B (cell_data, cell_data_hash, AutoSet<CellDependency>,
CounterMap<CollectibleRef, i32>) stay at I=1. `in_progress_cells` stays at
I=1 to avoid overflowing LazyField under the `hanging_detection` feature
(which inflates `Event` from 8 B to 16 B).

`TaskStorage` stays at 128 B, `LazyField` at 48 B; only inline capacities
change.
@lukesandberg lukesandberg force-pushed the 05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec branch from ed75068 to 3192aa5 Compare May 20, 2026 16:52
@lukesandberg lukesandberg merged commit 0555379 into canary May 20, 2026
163 of 164 checks passed
@lukesandberg lukesandberg deleted the 05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec branch May 20, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants